#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Windows;

namespace UTable.Objects.Policies
{
    public class ObjectOutDetector
    {
        protected IObject _container;
        protected List<IObject> _childContainers = new List<IObject>();
        protected List<IObject> _freezedObjects = new List<IObject>();


        protected void OnObjectOutDetected(IObject target, IObject destContainer)
        {
            if (ObjectOutDetected != null)
                ObjectOutDetected(this, new ObjectOutDetectedEventArgs(target, destContainer));
        }

        protected Boolean IsObjectWithinAnotherObject(IObject o1, IObject o2)
        {
            Point p1 = _container.TranslatePoint(o1.TranslatePoint(new Point(0, 0), _container), o2);
            Point p2 = _container.TranslatePoint(o1.TranslatePoint(new Point(o1.Width, 0), _container), o2);
            Point p3 = _container.TranslatePoint(o1.TranslatePoint(new Point(o1.Width, o1.Height), _container), o2);
            Point p4 = _container.TranslatePoint(o1.TranslatePoint(new Point(0, o1.Height), _container), o2);

            if (IsPointInObject(o2, p1) &&
                IsPointInObject(o2, p2) &&
                IsPointInObject(o2, p3) &&
                IsPointInObject(o2, p4))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        protected Boolean IsObjectOutOfCurrentContainer(IObject o)
        {
            if (!IsPointInObject(_container, o.TranslatePoint(new Point(0, 0), _container)) &&
                !IsPointInObject(_container, o.TranslatePoint(new Point(o.Width, 0), _container)) &&
                !IsPointInObject(_container, o.TranslatePoint(new Point(o.Width, o.Height), _container)) &&
                !IsPointInObject(_container, o.TranslatePoint(new Point(0, o.Height), _container)) &&
                !IsPointInObject(o, _container.TranslatePoint(new Point(0, 0), o)) &&
                !IsPointInObject(o, _container.TranslatePoint(new Point(_container.Width, 0), o)) &&
                !IsPointInObject(o, _container.TranslatePoint(new Point(_container.Width, _container.Height), o)) &&
                !IsPointInObject(o, _container.TranslatePoint(new Point(0, _container.Height), o)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        protected Boolean IsPointInObject(IObject o, Point p)
        {
            return (p.X > 0 && p.X < o.Width && p.Y > 0 && p.Y < o.Height);
        }

        public ObjectOutDetector(IObject container)
        {
            _container = container;
            (_container as IObjectEntity).Children.CollectionChanged += new NotifyCollectionChangedEventHandler(Children_CollectionChanged);
            foreach (IObject o in (_container as IObjectEntity).Children)
            {
                if (o is IObject)
                    _childContainers.Add(o as IObject);
            }
        }

        void Children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    OnObjectAdded(e.NewItems[0] as IObject);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    OnObjectRemoved(e.OldItems[0] as IObject);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    OnObjectAdded(e.NewItems[0] as IObject);
                    OnObjectRemoved(e.OldItems[0] as IObject);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    foreach (IObject child in e.OldItems)
                        OnObjectRemoved(child);
                    break;
            }
        }

        public virtual void OnObjectAdded(IObject child)
        {
            // Console.WriteLine(_container.ObjectType +   " " + child.ObjectType + " : Added");
            child.LayoutChanged += OnObjectLayoutChanged;
            if (child.CanHoldObjects)
                _childContainers.Add(child);
        }

        public virtual void OnObjectRemoved(IObject child)
        {
            // Console.WriteLine(_container.ObjectType + " " + child.ObjectType + " : Removed");
            child.LayoutChanged -= OnObjectLayoutChanged;
            if (_childContainers.Contains(child))
                _childContainers.Remove(child);
        }

        public virtual void OnObjectLayoutChanged(IObject obj, ObjectLayoutChangedEventArgs args)
        {
            // Console.WriteLine(_container.ObjectType + " " + "Layout Changed");
            CheckObject(obj);
        }

        public virtual void CheckObject(IObject target)
        {
            if (_freezedObjects.Contains(target))
                return;
            if (IsObjectOutOfCurrentContainer(target))
            {
                OnObjectOutDetected(target, UTableHelper.CurrentVisualTree.GetParent(_container) as IObject);
                return;
            }
            foreach (IObject c in _childContainers)
            {
                if (target != c && target.ZIndex > c.ZIndex && IsObjectWithinAnotherObject(target, c))
                {
                    OnObjectOutDetected(target, c);
                    return;
                }
            }
        }

        public virtual void CheckObjectOufOfContainer(IObject target)
        {
            if (_freezedObjects.Contains(target))
                return;
            if (IsObjectOutOfCurrentContainer(target))
            {
                OnObjectOutDetected(target, UTableHelper.CurrentVisualTree.GetParent(_container) as IObject);
            }
        }

        public virtual void CheckObjectInSubContainer(IObject target)
        {
            if (_freezedObjects.Contains(target))
                return;
            foreach (IObject c in _childContainers)
            {
                if (target != c && target.ZIndex > c.ZIndex && IsObjectWithinAnotherObject(target, c))
                {
                    OnObjectOutDetected(target, c);
                    return;
                }
            }
        }

        public ObjectOutDetectedEventArgs DetectObjectOut(IObject target)
        {
            if (IsObjectOutOfCurrentContainer(target))
            {
                return new ObjectOutDetectedEventArgs(target, 
                    UTableHelper.CurrentVisualTree.GetParent(_container) as IObject);
            }
            foreach (IObject c in _childContainers)
            {
                if (target != c && target.ZIndex > c.ZIndex && IsObjectWithinAnotherObject(target, c))
                {
                    return new ObjectOutDetectedEventArgs(target, c);
                }
            }
            return null;
        }

        public ObjectOutDetectedEventArgs DetectObjectOufOfContainer(IObject target)
        {
            if (IsObjectOutOfCurrentContainer(target))
            {
                return new ObjectOutDetectedEventArgs(target, 
                    UTableHelper.CurrentVisualTree.GetParent(_container) as IObject);
            }
            return null;
        }

        public ObjectOutDetectedEventArgs DetectObjectInSubContainer(IObject target)
        {
            foreach (IObject c in _childContainers)
            {
                if (target != c && target.ZIndex > c.ZIndex && IsObjectWithinAnotherObject(target, c))
                {
                    return new ObjectOutDetectedEventArgs(target, c);
                }
            }
            return null;
        }

        public virtual void FreezeObject(IObject obj)
        {
            if (!_freezedObjects.Contains(obj))
                _freezedObjects.Add(obj);
        }

        public virtual void UnfreezeObject(IObject obj)
        {
            if (_freezedObjects.Contains(obj))
                _freezedObjects.Remove(obj);
        }

        public event ObjectOutDetectedEventHandler ObjectOutDetected;
    }

    public class ObjectOutDetectedEventArgs
    {
        private IObject _target;

        private IObject _destContainer;

        public ObjectOutDetectedEventArgs(IObject _container, IObject destContainer)
        {
            _target = _container;
            _destContainer = destContainer;
        }

        public IObject Target
        {
            get
            {
                return _target;
            }
        }

        public IObject DestContainer
        {
            get
            {
                return _destContainer;
            }
        }
    }

    public delegate void ObjectOutDetectedEventHandler(Object sender, ObjectOutDetectedEventArgs args);
}
